﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using MDK.Common;
using MDK.Model.Models;

namespace MDK.Device
{
    public class PositionModel
    {
        public Position[] DesktopPositions { get; set; }
        public Position AtmBeltPosition { get; set; }
        public Position FreezerBeltPosition { get; set; }
        public Position WeighPosition { get; set; }
        public Position[][] CentrifugePositions { get; set; }
        public Position[] BalancePositions { get; set; }
        public Stack<Pallet>[] FreezerPositions { get; set; }
        public Pallet AtmToCameraPallet { get; set; }
        public ConcurrentQueue<Pallet> CameraToFrontPalletQueue { get; set; }
        public ConcurrentQueue<Pallet> FreezerBeltPalletQueue { get; set; }
        public ConcurrentQueue<Pallet> FrontToBackQueue { get; set; }
        public Position BackBeltPosition { get; set; }
        public Position DialInPosition { get; set; }
        public Position DialOutPosition { get; set; }
        public Position CollectPosition { get; set; }
        public Position BackCameraPosition { get; set; }
        public Position LipemiaOrHemolysisStore { get; set; }
        public Position[] BackStripPositions { get; set; }
        public Position[] BackYangpuPositions { get; set; }
        public Position[] Back2StripPositions { get; set; }
        public Position BiochemicalDialPosition { get; set; }
        public Position Hole96Position { get; set; }
        public Position Back2TempPosition { get; set; }
        public Position[] BackManPositions { get; set; }
        public Position[] AusbioPositions { get; set; }
        public Position[] TecanPositions { get; set; }

        private PositionModel()
        {
        }

        public static PositionModel Load()
        {
            var model = new PositionModel();
            model.FreezerPositions = Enumerable.Range(0, 6).Select(t => new Stack<Pallet>()).ToArray();
            model.CameraToFrontPalletQueue = new ConcurrentQueue<Pallet>();
            model.FreezerBeltPalletQueue = new ConcurrentQueue<Pallet>();
            model.FrontToBackQueue = new ConcurrentQueue<Pallet>();
            model.AtmToCameraPallet = null;

            var positions = IDatatoFlow.GetPosition();
            model.DesktopPositions = positions.Where(t => t.PositionType == 2).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.AtmBeltPosition = positions.FirstOrDefault(t => t.Id == "60001");
            model.FreezerBeltPosition = positions.FirstOrDefault(t => t.Id == "60002");
            model.WeighPosition = positions.FirstOrDefault(t => t.Id == "30001");
            var cenPos = positions.Where(t => t.PositionType == 11).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.CentrifugePositions = new Position[2][];
            model.CentrifugePositions[0] = new Position[4];
            model.CentrifugePositions[1] = new Position[4];
            model.CentrifugePositions[0][0] = new Position() {X = cenPos[0].X, Y = cenPos[0].Y, Z = cenPos[0].Z, SZ = cenPos[0].SZ, TubeSZ = cenPos[0].TubeSZ};
            model.CentrifugePositions[0][1] = new Position() {X = cenPos[0].X, Y = cenPos[0].Y, Z = cenPos[0].Z, SZ = cenPos[0].SZ, TubeSZ = cenPos[0].TubeSZ};
            model.CentrifugePositions[0][2] = new Position() {X = cenPos[0].X, Y = cenPos[0].Y, Z = cenPos[0].Z, SZ = cenPos[0].SZ, TubeSZ = cenPos[0].TubeSZ};
            model.CentrifugePositions[0][3] = new Position() {X = cenPos[0].X, Y = cenPos[0].Y, Z = cenPos[0].Z, SZ = cenPos[0].SZ, TubeSZ = cenPos[0].TubeSZ};
            model.CentrifugePositions[1][0] = new Position() {X = cenPos[1].X, Y = cenPos[1].Y, Z = cenPos[1].Z, SZ = cenPos[1].SZ, TubeSZ = cenPos[1].TubeSZ};
            model.CentrifugePositions[1][1] = new Position() {X = cenPos[1].X, Y = cenPos[1].Y, Z = cenPos[1].Z, SZ = cenPos[1].SZ, TubeSZ = cenPos[1].TubeSZ};
            model.CentrifugePositions[1][2] = new Position() {X = cenPos[1].X, Y = cenPos[1].Y, Z = cenPos[1].Z, SZ = cenPos[1].SZ, TubeSZ = cenPos[1].TubeSZ};
            model.CentrifugePositions[1][3] = new Position() {X = cenPos[1].X, Y = cenPos[1].Y, Z = cenPos[1].Z, SZ = cenPos[1].SZ, TubeSZ = cenPos[1].TubeSZ };

            model.BalancePositions = positions.Where(t => t.PositionType == 7).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            foreach (var position in model.BalancePositions)
            {
                position.Pallet = new Pallet()
                {
                    Tubes = Enumerable.Range(0, 6).Select(t => new Tube() { Type = TubeTypes.Balance }).ToArray()
                };
            }

            model.Back2StripPositions = positions.Where(t => t.Id.StartsWith("402") && t.PositionType == 4).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.BiochemicalDialPosition = positions.FirstOrDefault(t => t.PositionType == 13);
            model.Hole96Position = positions.FirstOrDefault(t => t.PositionType == 12);
            model.Back2TempPosition = positions.FirstOrDefault(t => t.PositionType == 14);
            
            model.BackBeltPosition = positions.FirstOrDefault(t => t.Id == "60003");
            model.BackCameraPosition = positions.FirstOrDefault(t => t.PositionType == (int)PositionTypes.LipemiaOrHemolysisCamere);
            model.CollectPosition = positions.FirstOrDefault(t => t.PositionType == (int)PositionTypes.Collect);
            model.LipemiaOrHemolysisStore = positions.FirstOrDefault(t => t.PositionType == (int)PositionTypes.LipemiaOrHemolysisStore);
            model.DialInPosition = positions.FirstOrDefault(t => t.Id == "50001");
            model.DialOutPosition = positions.FirstOrDefault(t => t.Id == "50002");
            model.BackStripPositions = positions.Where(t => t.Id.StartsWith("400") && t.PositionType == 4).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.BackManPositions = positions.Where(t => t.PositionType == 16).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.AusbioPositions = positions.Where(t => t.PositionType == 17).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.TecanPositions = positions.Where(t => t.PositionType == 18).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();
            model.BackYangpuPositions = positions.Where(t => t.PositionType == (int)PositionTypes.Yangpu).OrderBy(t => t.Id).Select(t => (Position)t).ToArray();

            var samples = IDatatoFlow.GetSamples();
            var exceptFreezer = samples.Where(t => t.PostionType != "1");
            foreach (var sample in exceptFreezer)
            {
                sample.Is_Delete = true;
                IDatatoFlow.SampleDelete(sample);
            }

            var freezerSample = samples.Where(t => t.PostionType == "1");
            // TODO: 冷藏赋值
            for (int i = 1; i <= 6; i++)
            {
                var select = i.ToString();
                var freezerSamples = freezerSample.Where(t => t.PostionId.EndsWith(select)).OrderByDescending(t => t.PostionId).ToList();
                for (int j = 4; j >0; j--)
                {
                    var select1 = "10" + i + "0" + j;
                    freezerSamples = freezerSample.Where(t => t.PostionId == select1).OrderByDescending(t => t.PostionId).ToList();
                    if (freezerSamples.Count > 0)
                    {
                        var Pallet = new Pallet() { State= PalletStatus.Storage, ActiveBarcode="TEST01"};
                        foreach (var item in freezerSamples)
                        {
                            Pallet.Tubes[(int)item.Index] = new Tube
                            {
                                Id = item.Id,
                                Barcode = item.DonCode,
                                Type = (TubeTypes)(int)item.SampleType,
                                State = (TubeStatus)(int)item.State,
                                BloodType = item.ABO,
                                GatherTime = item.GatherTime
                            };
                        }
                        model.FreezerPositions[i-1].Push(Pallet);
                    }
                }
            }

            //            model.BiochemicalDialPosition = new Position
            //            {
            //                Type = PositionTypes.BiochemicalDial,
            //                X = 190.28,
            //                Y = 227.43,
            //                SZ = 3,
            //                TubeSZ = 3
            //            };
            //            model.Hole96Position = new Position
            //            {
            //                Type = PositionTypes.Hole96,
            //                X = 563.8,
            //                Y=112,
            //                SZ = 5,
            //                TubeSZ = 5
            //            };

            return model;
        }

        public Position AllocFreePosition(SystemModel SysModel)
        {
            Position position = null;
            do
            {
                position = DesktopPositions.FirstOrDefault(t => t.Pallet == null);
                while (position == null)
                {
                   
                    FlowEventHelper.SendMessage(new HandoverStateChangedArgs() { Code = FlowMesssages.FrontPositionUseUp, Detail = "台面工位耗尽", Message = "台面工位全部耗尽，请人工处理,清空台面补空托" }, SysModel);
                    position = DesktopPositions.FirstOrDefault(t => t.Pallet == null);
                }
                var Id = int.Parse(position.Id.Substring(position.Id.Length - 2, 2));
                if (SysModel.Front.PositionSensors[Id - 1].IsActive())
                {
                    position.Pallet = new Pallet()
                    {
                        State = PalletStatus.WaitToFreezer
                    };
                }
            } while (position.Pallet != null);
            return position;
        }

        public int AllocFreezerAisleForIn(TubeTypes palletType)
        {
            var aisle = FreezerPositions.FirstOrDefault(t => t.Any(s => s.Type == palletType) && t.Count < 4) 
                        ?? FreezerPositions.FirstOrDefault(t => t.Count == 0);

            if (aisle == null)
            {
                return -1;
            }

            return Array.IndexOf(FreezerPositions, aisle);
        }

        public void FreezerPositionChanged(int targetAisle)
        {
            var targerStack = FreezerPositions[targetAisle].ToArray();
            for (int i = 0; i < targerStack.Length; i++)
            {
                var pallet = targerStack[i];
                for (int j = 0; j < pallet.Tubes.Length; j++)
                {
                    var tube = pallet.Tubes[j];
                    IDatatoFlow.FreezerSampleUpdate(tube.Id, targetAisle, i);
                }
            }
        }

        public Position AllocEmptyPallet(SystemModel SysModel)
        {
            //传感器清空拿走的空托
            var list = DesktopPositions.Where(t => t.Pallet != null && t.Pallet.TubeCount == 0 && t.Pallet.State == PalletStatus.WaitToFreezer);
            foreach (var item in list)
            {
                var Id = int.Parse(item.Id.Substring(item.Id.Length - 2, 2));
                if (!SysModel.Front.PositionSensors[Id - 1].IsActive())
                {
                    item.Pallet = null;
                }
            }
            return DesktopPositions.FirstOrDefault(t => t.Pallet != null && t.Pallet.Type == TubeTypes.None);
        }

        public List<Position> GroupTubeForCentrifuge(out int tubeCount, SystemModel SysModel, Action action,bool IsCorrelations)
        {
            tubeCount = 0;
            // 待离心未分组排序
            var groupPositions = DesktopPositions.Where(t => t.Pallet != null && t.Pallet.State == PalletStatus.WaitBalance && string.IsNullOrEmpty(t.Pallet.GroupId))
                .OrderByDescending(t => t.Pallet.TubeCount).ToList();
            // 统计前80根试管所在托并移除多余
            for (int i = 0; i < groupPositions.Count; i++)
            {
                tubeCount += groupPositions[i].Pallet.TubeCount;
                if (tubeCount >= 80)
                {
                    groupPositions.RemoveRange(i + 1, groupPositions.Count - i - 1);
                    tubeCount = 80;
                    break;
                }
            }

            // 托不足则补空托
            if (groupPositions.Count == 0)
            {
                return null;
            }
            if (groupPositions.Count < 4)
            {
                //传感器清空拿走的空托
                var list = DesktopPositions.Where(t => t.Pallet != null && t.Pallet.TubeCount == 0 && t.Pallet.State == PalletStatus.WaitToFreezer);
                foreach(var item in list)
                {
                    var Id = int.Parse(item.Id.Substring(item.Id.Length - 2, 2));
                    if (!SysModel.Front.PositionSensors[Id - 1].IsActive())
                    {
                        item.Pallet = null;
                    }
                }

                groupPositions.AddRange(DesktopPositions.Where(t => t.Pallet != null && t.Pallet.TubeCount == 0 && t.Pallet.State == PalletStatus.WaitToFreezer).Take(4 - groupPositions.Count));
            }
            if (groupPositions.Count < 4)
            {
                if (DesktopPositions.All(t => t.Pallet == null || t.Pallet.State != PalletStatus.WaitScanBarcode) && IsCorrelations)
                {
                    action();
                    FlowEventHelper.SendMessage(new HandoverStateChangedArgs() { Code = FlowMesssages.FrontBalanceFailed, Detail = "无法完成配平", Message = "待离心的试管托不足4个，且没有足够的空托配平" },SysModel);
                }
                return null;
            }

            return groupPositions;
        }

        public Position AllocBalanceTube(out int hole)
        {
            hole = -1;
            var pos = BalancePositions.FirstOrDefault(t => t.Pallet != null && t.Pallet.TubeCount > 0);
            if (pos != null)
            {
                hole = Array.IndexOf(pos.Pallet.Tubes, pos.Pallet.Tubes.FirstOrDefault(s => s.Type == TubeTypes.Balance));
            }
            return pos;
        }

        public Position AllocEmptyHole(TubeTypes type, out int hole)
        {
            hole = -1;
            var pos = type == TubeTypes.Balance
                ? BalancePositions.FirstOrDefault(t => t.Pallet != null && t.Pallet.EmptyHoleCount > 0)
                : DesktopPositions.Where(t => t.Pallet != null && t.Pallet.Type == type).OrderBy(t => t.Pallet.TubeCount).FirstOrDefault();
            if (pos != null)
            {
                hole = Array.IndexOf(pos.Pallet.Tubes, pos.Pallet.Tubes.FirstOrDefault(s => s.Type == TubeTypes.None));
            }
            return pos;
        }

        public Position[] AllocCentrifugeGroup()
        {
            var group = DesktopPositions.Where(t => t.Pallet != null && t.Pallet.State == PalletStatus.WaitCentrifuge && !string.IsNullOrWhiteSpace(t.Pallet.GroupId)).GroupBy(t => t.Pallet.GroupId).FirstOrDefault();
            return group?.OrderBy(t => t.Pallet.GroupIndex).ToArray();
        }

        public Position CollectBalanceTube(out int hole)
        {
            hole = -1;

            foreach (var position in DesktopPositions)
            {
                if (position.Pallet == null || position.Pallet.State != PalletStatus.WaitToFreezer) continue;
                var tube = position.Pallet.Tubes.FirstOrDefault(t => t.Type == TubeTypes.Balance);
                if (tube == null) continue;
                hole = Array.IndexOf(position.Pallet.Tubes, tube);
                return position;
            }

            return null;
        }

        public Pallet MockPallet(PalletStatus palletState, int[] meimainTubes = null, int[] hesuanTubes = null, int[] positionTubes = null)
        {
            var pallet = new Pallet();
            pallet.Tubes = Enumerable.Range(0, 20).Select(t => new Tube() {Type = TubeTypes.None}).ToArray();
            pallet.State = palletState;
            if (meimainTubes != null && meimainTubes.Length > 0)
            {
                foreach (var tube in meimainTubes)
                {
                    pallet.Tubes[tube].Type = TubeTypes.Meimian;
                }
            }
            if (hesuanTubes != null && hesuanTubes.Length > 0)
            {
                foreach (var tube in hesuanTubes)
                {
                    pallet.Tubes[tube].Type = TubeTypes.Hesuan;
                }
            }
            if (positionTubes != null && positionTubes.Length > 0)
            {
                foreach (var tube in positionTubes)
                {
                    pallet.Tubes[tube].Type = TubeTypes.Positive;
                }
            }

            return pallet;
        }
    }

    public enum PalletStatus
    {
        WaitScanBarcode,
        WaitBalance,
        WaitCentrifuge,
        WaitToFreezer,
        Storage,
        WaitToDesktop,
        WaitToBackman,
        WaitToAusbio,
        WaitToTecen,
    }

    public enum TubeTypes
    {
        Unknown = -1,
        None,
        Meimian,
        Hesuan,
        Positive,
        Mixed,
        Balance,
    }

    [Flags]
    public enum TubeStatus
    {
        Checked = 1,
        ALT = 2,
        TP = 4,
        HIV = 8,
        HCV = 10,
        HBV = 20
    }

    public enum PositionTypes
    {
        Freezer = 1,
        Desktop =2,
        Weigh = 3,
        Strip = 4,
        Dial = 5,
        Belt = 6,
        Balance = 7,
        LipemiaOrHemolysisCamere = 8,
        LipemiaOrHemolysisStore = 9,
        Collect = 10,
        Centrifuge = 11,
        Hole96 = 12,
        BiochemicalDial = 13,
        Back2Temp = 14,
        Yangpu = 15,
        BeckMan=16,
        Ausbio =17,
        Tecan =18
    }

    public class Position
    {
        public string Id { get; set; }

        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
        public double SZ { get; set; }
        public double TubeSZ { get; set; }

        public PositionTypes Type { get; set; }

        public Pallet Pallet { get; set; }

        public static implicit operator Position(T_Position dbPosition)
        {
            return new Position()
            {
                Id = dbPosition.Id,
                X = (double) dbPosition.X,
                Y = (double) dbPosition.Y,
                Z = (double) dbPosition.Z,
                SZ = (double) dbPosition.SZ,
                TubeSZ = (double) dbPosition.TubeSZ,
                Type = (PositionTypes) dbPosition.PositionType,
                Pallet = null
            };
        }

        private static readonly int[] DesktopHoleXOffset = { -21, +00, +21, -42, -21, +00, +21, +42, -42, -21, +21, +42, -42, -21, +00, +21, +42, -21, +00, +21 };
        private static readonly int[] DesktopHoleYOffset = { -42, -42, -42, -21, -21, -21, -21, -21, +00, +00, +00, +00, +21, +21, +21, +21, +21, +42, +42, +42 };
        private static readonly int[] BackBeltHoleXOffset = { -42, -42, -42, -21, -21, -21, -21, -21, 0, 0, 0, 0, 21, 21, 21, 21, 21, 42, 42, 42 };
        private static readonly int[] BackBeltHoleYOffset = { -21, 0, 21, -42, -21, 0, 21, 42, -42, -21, 21, 42, -42, -21, 0, 21, 42, -21, 0, 21 };
        private static readonly double[] BiochemicalDialR = { 194.76, 165.0289721 };
        private static readonly double[] BiochemicalCoordAngleOffset = { 1.2458428095605333865958197710157, 0.98601650987067283693948082416544 };

        public double GetHoleX(int index)
        {
            switch (Type)
            {
                case PositionTypes.Balance: return X + 32 * (index % 6);
                case PositionTypes.Belt: return X + BackBeltHoleXOffset[index];
                case PositionTypes.Desktop: return X + DesktopHoleXOffset[index];
                case PositionTypes.BeckMan:
                case PositionTypes.Ausbio:
                case PositionTypes.Tecan:
                case PositionTypes.Strip: return X;
                case PositionTypes.Hole96: return X + (index / 12) * 21;
                case PositionTypes.BiochemicalDial: return X - BiochemicalDialR[index / 55] * Math.Cos(BiochemicalCoordAngleOffset[index / 55] + (index % 55) * 2 * Math.PI / 55);
                case PositionTypes.LipemiaOrHemolysisStore: return X + 32 * (index % 2);
                case PositionTypes.Yangpu: return X + (index % 5) * 25.73;
                default: return X;
            }
        }

        public double GetHoleY(int index)
        {
            switch (Type)
            {
                case PositionTypes.Balance: return Y + 32 * (index / 6);
                case PositionTypes.Belt: return Y + BackBeltHoleYOffset[index];
                case PositionTypes.Desktop: return Y + DesktopHoleYOffset[index];
                case PositionTypes.Strip: return Y + index * 18.06;
                case PositionTypes.Hole96: return Y + (index % 12) * 21;
                case PositionTypes.BiochemicalDial: return Y + BiochemicalDialR[index / 55] * Math.Sin(BiochemicalCoordAngleOffset[index / 55] + (index % 55) * 2 * Math.PI / 55);
                case PositionTypes.LipemiaOrHemolysisStore: return Y + 32 * (index / 2);
                case PositionTypes.Yangpu: return Y + (index / 5) * 18.22;
                case PositionTypes.BeckMan: return Y + index * 17.40;
                case PositionTypes.Ausbio: return Y + index * 14.98;
                case PositionTypes.Tecan: return Y + index * 18.37;
                default: return Y;
            }
        }

        public int GetEmptyHole()
        {
            return Pallet == null ? -1 : Array.IndexOf(Pallet.Tubes, Pallet.Tubes.FirstOrDefault(t => t.Type == TubeTypes.None));
        }

        public int GetTubeHole()
        {
            return Pallet == null ? -1 : Array.IndexOf(Pallet.Tubes, Pallet.Tubes.FirstOrDefault(t => t.Type != TubeTypes.None));
        }
    }

    public class Pallet
    {
        private string _activeBarcode;

        public string ActiveBarcode
        {
            get { return _activeBarcode; }
            set
            {
                if (_activeBarcode != value)
                {
                    Rotate(value);
                    _activeBarcode = value;
                }
            }
        }

        public Pallet()
        {
            Tubes = Enumerable.Range(0, 20).Select(t => new Tube() {Type = TubeTypes.None}).ToArray();
        }

        public TubeTypes Type
        {
            get
            {
                if (Tubes.All(t => t.Type == TubeTypes.None)) return TubeTypes.None;
                if (Tubes.All(t => t.Type == TubeTypes.None || t.Type == TubeTypes.Hesuan)) return TubeTypes.Hesuan;
                if (Tubes.All(t => t.Type == TubeTypes.None || t.Type == TubeTypes.Meimian)) return TubeTypes.Meimian;
                if (Tubes.All(t => t.Type == TubeTypes.None || t.Type == TubeTypes.Positive)) return TubeTypes.Positive;
                return TubeTypes.Mixed;
            }
        }

        public TubeTypes ExceptType
        {
            get { return MeimianTubeCount > HesuanTubeCount ? TubeTypes.Meimian : TubeTypes.Hesuan; }
        }
        public string HandoverId { get; set; }
        public Tube[] Tubes { get; set; } 

        public string Extra { get; set; }

        public bool NeedCentrifuge { get; set; }

        public bool Priority { get; set; }

        public PalletStatus State { get; set; }

        public string GroupId { get; set; }

        public int GroupIndex { get; set; }

        public DateTime MinGatherTime
        {
            get { return Tubes.Min(t => t.GatherTime); }
        }

        public int TubeCount
        {
            get { return Tubes.Count(s => s.Type != TubeTypes.None); }
        }

        public int EmptyHoleCount
        {
            get { return Tubes.Count(s => s.Type == TubeTypes.None); }
        }

        public int NoBarcodeCount
        {
            get { return Tubes.Count(s => s.Type != TubeTypes.None && string.IsNullOrWhiteSpace(s.Barcode)); }
        }

        public int MeimianTubeCount
        {
            get { return Tubes.Count(s => s.Type == TubeTypes.Meimian); }
        }

        public int HesuanTubeCount
        {
            get { return Tubes.Count(s => s.Type == TubeTypes.Hesuan); }
        }

        private void Rotate(string newRefBarcode)
        {
            var rotateTimes = 0;
            if (!string.IsNullOrWhiteSpace(ActiveBarcode) && !string.IsNullOrWhiteSpace(newRefBarcode))
            {
                var ob = int.Parse(ActiveBarcode.Substring(ActiveBarcode.Length - 2, 2));
                var cb = int.Parse(newRefBarcode.Substring(newRefBarcode.Length - 2, 2));
                if (cb < ob) cb += 4;
                rotateTimes = (cb - ob);
            }

            // 旋转
            while (rotateTimes > 0)
            {
                Tube tmp = null;
                // 0 => 12, 12 => 19, 19 => 7, 7 => 0
                tmp = Tubes[7];
                Tubes[7] = Tubes[19];
                Tubes[19] = Tubes[12];
                Tubes[12] = Tubes[0];
                Tubes[0] = tmp;
                // 1 => 8, 8 => 18, 18 => 11, 11 => 1
                tmp = Tubes[11];
                Tubes[11] = Tubes[18];
                Tubes[18] = Tubes[8];
                Tubes[8] = Tubes[1];
                Tubes[1] = tmp;

                // 2 => 3, 3 => 17, 17 => 16, 16 => 2
                tmp = Tubes[16];
                Tubes[16] = Tubes[17];
                Tubes[17] = Tubes[3];
                Tubes[3] = Tubes[2];
                Tubes[2] = tmp;

                // 4 => 13, 13 => 15, 15 => 6, 6 => 4
                tmp = Tubes[6];
                Tubes[6] = Tubes[15];
                Tubes[15] = Tubes[13];
                Tubes[13] = Tubes[4];
                Tubes[4] = tmp;

                // 5 => 9, 9 => 14, 14 => 10, 10 => 5
                tmp = Tubes[10];
                Tubes[10] = Tubes[14];
                Tubes[14] = Tubes[9];
                Tubes[9] = Tubes[5];
                Tubes[5] = tmp;
                rotateTimes--;
            }
        }
        
    }

    public class Tube
    {
        public string Id { get; set; }

        public static Tube Empty
        {
            get { return new Tube(); }
        }

        public Tube()
        {
            Type = TubeTypes.None;
            GatherTime = new DateTime(2000, 1, 1);
        }

        public string Barcode { get; set; }

        public TubeTypes Type { get; set; }

        public TubeStatus State { get; set; }

        public string BloodType { get; set; }

        public DateTime GatherTime { get; set; }

        public int Stripindex { get; set; }

        public override string ToString()
        {
            return $"{Barcode},{Type},{State},{BloodType},{GatherTime}";
        }
    }
}
